import { Lexer, Token } from "../ch2";


/**
 * 特定场景的词法解析器
 */
 class LookaheadLexer extends Lexer {
    // 注册Token
    static NAME = 2;
    static COMMA = 3;
    static LBRACK = 4;
    static RBRACK = 5;
    static EQUALS = 6;

    static tokenNames:string[] = [
        'n/a',
        "<EOF>",
        "NAME",
        "COMMA",
        "LBRACK",
        "RBRACK",
        "EQUALS"
    ]
    
    /**
     * 依次获取Token
     * @returns 
     */
    nextToken(): Token {
       while(this.c != Lexer.EOF){
            switch(this.c){
                case ' '  : 
                case '\t' : 
                case '\n' :
                case '\r' :
                    this.WS();
                    break;
                
                case ',' :
                    this.consume();
                    return new Token(LookaheadLexer.COMMA,',')
                case '[':
                    this.consume();
                    return new Token(LookaheadLexer.LBRACK,'[')
                case ']':
                    this.consume();
                    return new Token(LookaheadLexer.RBRACK,']')
                case '=':
                    this.consume();
                    return new Token(LookaheadLexer.EQUALS,'=')
                default:
                    if(this.isLETTER()){
                        return this.Name()
                    }else{
                        throw new Error("invalid char: " + this.c)
                    }
            }
       }

       return new Token(LookaheadLexer.EOF_TYPE,"<EOF>");
    }
    isLETTER(){
        return (this.c >= 'a' && this.c <= 'z')
        || (this.c >= 'A' && this.c <= 'Z')
    }
    Name(){
        let buf = '';
        do {
            buf = buf + this.c;
            this.consume();
        }while(this.isLETTER())

        return new Token(LookaheadLexer.NAME,buf);
    }
    WS(){
        let whiteSpace = [' ','\t','\n','\r']
        while (
            whiteSpace.indexOf(this.c) > -1
        ){
            this.consume();
        }
    }

    getTokenName(tokenType: number): string {
        return LookaheadLexer.tokenNames[tokenType];
    }

}

class Parser {
    input:Lexer
    //向前看的Token数组
    lookahead:Token[]
    //向前看k个 也就是lookahead的长度
    k:number;
    p:number = 0;

    constructor(input:Lexer,k:number){
        this.input = input;
        this.k = k;
        this.lookahead = new Array(this.k);

        for(let i = 1; i<=k ;i++){
            this.consume();
        }
    }

    /**
     * 向前移动到下一个符号 并且缓存到lookahead
     * 使用取余的环形索引
     */
    consume(){
        //p=0 k=3 lookahead[0] = token0;
        //p=1 k=3 lookahead[1] = token1;
        //p=2 k=3 lookahead[2] = token2;

        //p=0 k=3 lookahead[0] = token3;
        //p=1 k=3 lookahead[1] = token4; 
        this.lookahead[this.p] = this.input.nextToken();
        this.p = (this.p + 1) % this.k;
    }

    /**
     * 向前看第i个符号
     * @param i 
     * @returns 
     */
    LT(i:number){
        // 这里也使用环形索引
        let temp = (this.p + i -1) % this.k;
        return this.lookahead[temp]
    }
    /**
     * 向前看第i个符号的类型
     * @param i 
     * @returns 
     */
    LA(i:number){
        return this.LT(i).type
    }

    match(tokenType:number){
        if(this.LA(1) == tokenType){
            this.consume();
        }else{
            console.log("---expecting",this.input.getTokenName(tokenType));
            console.log("---but found",this.LT(1));

            throw new Error("match error ");
        }
    }
}

class lookaheadParser extends Parser {
     // list : '[' elements ']'
     list(){
        this.match(LookaheadLexer.LBRACK);
        this.elements();
        this.match(LookaheadLexer.RBRACK);
    }
    // elements : element (',' element)*
    elements(){
        this.element();
        while(this.LA(1) == LookaheadLexer.COMMA){
            this.match(LookaheadLexer.COMMA);
            this.element();
        }
    }
    // element : NAME | NAME '=' NAME | list
    element(){
        if(this.LA(1) == LookaheadLexer.NAME && this.LA(2) == LookaheadLexer.EQUALS){
            this.match(LookaheadLexer.NAME);
            this.match(LookaheadLexer.EQUALS);
            this.match(LookaheadLexer.NAME)
        }else if(this.LA(1) == LookaheadLexer.NAME){
            this.match(LookaheadLexer.NAME);
        }else if(this.LA(1) == LookaheadLexer.LBRACK){
            this.list();
        }else {
            throw new Error("expectin name or list found " + this.LT(1))
        }
    }
}

export {
    LookaheadLexer,
    lookaheadParser
}